//
// Created by Administrator on 2016/9/6.
//

#include <iostream>
#include "PKGUtil.h"

using namespace ZainTL;
using namespace std;

string PKGUtil::pkg(const char *path, const char *outputPath, bool bEncrypt) {
    File *inFile = new File(path);
    FileStat fileStat = inFile->getFileStat();
    string finalOutputPathString;
    if (outputPath == NULL) {
        string normlizedPath = inFile->getAbsolutePath();
        if (fileStat == Is_Dir) {
            finalOutputPathString = normlizedPath.append("/out.zpk");
        } else if (fileStat == Is_File) {
            string before = normlizedPath.substr(0, normlizedPath.find_last_of("/"));
            finalOutputPathString = before.append("out.zpk");
        }
    } else {
        finalOutputPathString = outputPath;
    }
    File *outFile = new File(finalOutputPathString.c_str());
    pkgImpl(inFile, outFile, bEncrypt);
    delete (inFile);
    delete (outFile);
    return finalOutputPathString;
}

void PKGUtil::unpkg(const char *path, function<void(FileDatas &)> onFileData, bool bDecrypt) {
    File *file = new File(path);
    FileInputStream *fileInputStream = file->getFileInputStream();
    while (!fileInputStream->eof()) {
        FileType fileType = (FileType) fileInputStream->getChar();
        uint16_t pathLen = fileInputStream->getShort();
        char *pathBuffer = new char[pathLen];
        fileInputStream->read(pathBuffer, pathLen);

        uint64_t dataLen = fileInputStream->getLong();
        char *dataBuffer = new char[dataLen + 1];
        fileInputStream->read(dataBuffer, dataLen);
        if (bDecrypt) {
            decrypt(dataBuffer, dataLen, [=](char *rs) {
                memcpy(dataBuffer, rs, dataLen);
            });
        }
        dataBuffer[dataLen] = '\0';

        FileDatas fileDatas;
        fileDatas.fileType = fileType;
        fileDatas.path = pathBuffer;
        fileDatas.data = dataBuffer;
        fileDatas.dataLen = dataLen;
        onFileData(fileDatas);
    }
    delete (file);
}

void PKGUtil::pkgImpl(File *inFile, File *outFile, bool bEncrypt) {
    if (inFile->getAbsolutePath() == outFile->getAbsolutePath())
        return;
    OutputStream *outputStream = outFile->getFileOutputStream();
    switch (inFile->getFileStat()) {
        default:
            break;
        case Is_File: {
            string filePath = inFile->getRelativePath();
            char buffer[2048];
            /*file type*/
            outputStream->writeChar(getFileType(filePath));

            /*file path length*/
            outputStream->writeShort(filePath.length() + 1);

            /*file path content*/
            outputStream->writeString(filePath);

            /*file content length*/
            InputStream *inputStream = inFile->getFileInputStream();
            outputStream->writeLong(inputStream->available());

            /*file content*/
            uint32_t readCount;
            if (bEncrypt) {
                while ((readCount = inputStream->read(buffer, 2048)) != 0) {
                    encrypt(buffer, readCount, [=](char *rs) {
                        outputStream->write(rs, readCount);
                    });
                }
            } else {
                while ((readCount = inputStream->read(buffer, 2048)) != 0) {
                    outputStream->write(buffer, readCount);
                }
            }
        }
            break;
        case Is_Dir:
            inFile->listChildren([=](File *pFile) { pkgImpl(pFile, outFile, bEncrypt); });
            break;
    }
}

FileType PKGUtil::getFileType(std::string &filePath) {
    string ext = filePath.substr(filePath.find_last_of(".") + 1);
    transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
    if (ext == "jpg" || ext == "png" || ext == "jpeg") {
        return FileType::img;
    } else if (ext == "fbx" || ext == "obj") {
        return FileType::model;
    } else if (ext == "mp3" || ext == "wav") {
        return FileType::audio;
    } else {
        return FileType::txt;
    }
}

void PKGUtil::encrypt(const char *origin, uint64_t count, std::function<void(char *)> onEncrypted) {
    uint16_t keyLen = mEncryptionKey.length() + 1;
    uint32_t index = keyLen;
    vector<char> keys;
    while (--index > 0) {
        keys.push_back(mEncryptionKey[index]);
    }
    char *rs = new char[count];
    memcpy(rs, origin, count);
    for (uint32_t i = 0; i < count; ++i) {
        rs[i] += i | keys[i % keyLen];
    }
    onEncrypted(rs);
    delete[](rs);
}

void PKGUtil::decrypt(const char *origin, uint64_t count, std::function<void(char *)> onDecrypted) {
    uint16_t keyLen = mEncryptionKey.length() + 1;
    uint32_t index = keyLen;
    vector<char> keys;
    while (--index > 0) {
        keys.push_back(mEncryptionKey[index]);
    }
    char *rs = new char[count];
    memcpy(rs, origin, count);
    for (uint32_t i = 0; i < count; ++i) {
        rs[i] -= i | keys[i % keyLen];
    }
    onDecrypted(rs);
    delete[](rs);
}

void PKGUtil::setEncryptionKey(const char *key) {
    mEncryptionKey = key;
}
